
#include "matrix.h"
#include <cmath>
#include <stdexcept>

SquareMatrix::SquareMatrix(int dimension) :
	_dimension(dimension), _matrix(dimension * dimension, 0)
{}

SquareMatrix::SquareMatrix(const SquareMatrix &other) :
	_dimension(other._dimension), _matrix(other._matrix)
{}

SquareMatrix &SquareMatrix::operator=(const SquareMatrix &other) {
	_dimension = other._dimension;
	_matrix = other._matrix;
	return *this;
}

SquareMatrix SquareMatrix::identity(int dimension) {
	SquareMatrix result(dimension);
	for (int i = 0, j = 0; i < dimension; i++, j++) {
		result._matrix[i * dimension + j] = 1;
	}
	return result;
}

MatrixElement &SquareMatrix::operator()(int row, int col) {
	row -= IndexOffset;
	col -= IndexOffset;

	if (row * _dimension + col >= _dimension * _dimension) throw std::out_of_range("SquareMatrix::operator()(): index out of range");

	return _matrix[row * _dimension + col];
}

SquareMatrix SquareMatrix::operator+(const SquareMatrix &other) {
	if (_dimension != other._dimension) throw std::domain_error("SquareMatrix::operator+(): unable to add matrices of different dimensions");
	SquareMatrix result(_dimension);
	int sz = _dimension * _dimension;
	for (int i = 0; i < sz; i++) {
		result._matrix[i] = _matrix[i] + other._matrix[i];
	}
	return result;
}

SquareMatrix SquareMatrix::operator-(const SquareMatrix &other) {
	if (_dimension != other._dimension) throw std::domain_error("SquareMatrix::operator-(): unable to subtract matrices of different dimensions");
	SquareMatrix result(_dimension);
	int sz = _dimension * _dimension;
	for (int i = 0; i < sz; i++) {
		result._matrix[i] = _matrix[i] - other._matrix[i];
	}
	return result;
}

SquareMatrix SquareMatrix::operator*(MatrixElement factor) {
	SquareMatrix result(_dimension);
	int sz = _dimension * _dimension;
	for (int i = 0; i < sz; i++) {
		result._matrix[i] = _matrix[i] * factor;
	}
	return result;
}

std::vector<MatrixElement> SquareMatrix::operator*(const std::vector<MatrixElement> &vec) {
	if (_dimension != vec.size()) throw std::domain_error("SquareMatrix::operator*(): unable to multiply matrices of different dimensions");
	std::vector<MatrixElement> result(_dimension);
	int sz = _dimension;
	for (int i = 0; i < sz; i++) {
		for (int j = 0; j < sz; j++) {
			result[i] += _matrix[i * _dimension + j] * vec[j];
		}
	}
	return result;
}

SquareMatrix SquareMatrix::operator*(const SquareMatrix &other) {
	if (_dimension != other._dimension) throw std::domain_error("SquareMatrix::operator*(): unable to multiply matrices of different dimensions");
	SquareMatrix result(_dimension);
	int sz = _dimension;
	for (int i = 0; i < sz; i++) {
		for (int j = 0; j < sz; j++) {
			for (int k = 0; k < sz; k++) {
				result._matrix[i * _dimension + j] += _matrix[i * _dimension + k] * other._matrix[k * _dimension + j];
			}
		}
	}
	return result;
}

int SquareMatrix::getDimension() const {
	return _dimension;
}

SquareMatrix operator*(MatrixElement factor, SquareMatrix &mat) {
	return mat * factor;
}

std::ostream &operator<<(std::ostream &out, SquareMatrix &mat) {
	int sz = mat._dimension;
	for (int i = 0; i < sz; i++) {
		out << (std::fabs(mat._matrix[i * mat._dimension]) < AllowedEps ? 0 : mat._matrix[i * mat._dimension]);
		for (int j = 1; j < sz; j++) {
			int idx = i * mat._dimension + j;
			out << ' ' << (std::fabs(mat._matrix[idx]) < AllowedEps ? 0 : mat._matrix[idx]);
		}
		out << '\n';
	}
	return out;
}

std::istream &operator>>(std::istream &in, SquareMatrix &mat) {
	int dim;
	in >> dim;
	SquareMatrix result(dim);
	int sz = dim * dim;
	for (int i = 0; i < sz; i++) {
		in >> result._matrix[i];
	}
	mat = result;
	return in;
}
